//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-06-01     ZhangYihua   first version
//
// Description  : 
//################################################################################

module add_all #(
parameter           OPRD_BW                 = 8,    // bit width of single operand
parameter           OPRD_NUM                = 16,   // number of operands, must 1<=OPRD_NUM<=128
parameter           OPRD_SIGNED             = 1'b0, // 1'b0: unsigned; 1'b1: signed;
parameter           LVL_NUM                 = ((OPRD_NUM<=1) ? 1 : $clog2(OPRD_NUM)),
parameter [LVL_NUM-1:0]     LVL_REG         = {LVL_NUM{1'b0}},

// the following parameters are calculated automatically
parameter           SUM_BW                  = (OPRD_NUM<=1)       ? OPRD_BW : 
                                              (OPRD_SIGNED==1'b0) ? $clog2(((1<<OPRD_BW)-1)*OPRD_NUM+1) :
                                                                    OPRD_BW+$clog2(OPRD_NUM)
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input                                       vld_in,
input               [OPRD_BW*OPRD_NUM-1:0]  oprds,

output                                      vld_out,
output              [SUM_BW-1:0]            sum_o
);

//################################################################################
// define local varialbe and localparam
//################################################################################

localparam          L1_PAIR                 = OPRD_NUM/2;
localparam          L1_ODD                  = OPRD_NUM%2;
localparam          L1_NUM                  = L1_PAIR + L1_ODD;
localparam          L1_BW                   = OPRD_BW+((OPRD_NUM<=1) ? 0 : 1);

//################################################################################
// main
//################################################################################

wire                                        l1_vld;
wire                [L1_NUM*L1_BW-1:0]      l1_add;

add_pair #(
        .OPRD_BW                        (OPRD_BW                        ),	// bit width of single operand
        .OPRD_NUM                       (OPRD_NUM                       ),	// number of operands, must 2<=OPRD_NUM<=16
        .OPRD_SIGNED                    (OPRD_SIGNED                    ),
        .OPRD_REG                       (LVL_REG[0]                     )
) u_l1_add ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .vld_in                         (vld_in                         ),
        .oprds                          (oprds                          ),

        .vld_out                        (l1_vld                         ),
        .sums                           (l1_add                         )
);

generate if (OPRD_NUM<=2) begin:G_L1
    assign vld_out = l1_vld;
    assign sum_o = l1_add[0+:SUM_BW];
end else begin:G_L2
    localparam          L2_PAIR                 = L1_NUM/2;
    localparam          L2_ODD                  = L1_NUM%2;
    localparam          L2_NUM                  = L2_PAIR + L2_ODD;
    localparam          L2_BW                   = L1_BW+1;

    wire                                        l2_vld;
    wire                [L2_NUM*L2_BW-1:0]      l2_add;

    add_pair #(
            .OPRD_BW                        (L1_BW                          ),	// bit width of single operand
            .OPRD_NUM                       (L1_NUM                         ),	// number of operands, must 2<=OPRD_NUM<=16
            .OPRD_SIGNED                    (OPRD_SIGNED                    ),
            .OPRD_REG                       (LVL_REG[1]                     )
    ) u_l2_add ( 
            .rst_n                          (rst_n                          ),
            .clk                            (clk                            ),
            .cke                            (cke                            ),

            .vld_in                         (l1_vld                         ),
            .oprds                          (l1_add                         ),

            .vld_out                        (l2_vld                         ),
            .sums                           (l2_add                         )
    );
    
    if (OPRD_NUM<=4) begin:G_L2O
        assign vld_out = l2_vld;
        assign sum_o = l2_add[0+:L2_BW];
    end else begin:G_L3
        localparam          L3_PAIR                 = L2_NUM/2;
        localparam          L3_ODD                  = L2_NUM%2;
        localparam          L3_NUM                  = L3_PAIR + L3_ODD;
        localparam          L3_BW                   = L2_BW+1;

        wire                                        l3_vld;
        wire                [L3_NUM*L3_BW-1:0]      l3_add;

        add_pair #(
                .OPRD_BW                        (L2_BW                          ),	// bit width of single operand
                .OPRD_NUM                       (L2_NUM                         ),	// number of operands, must 2<=OPRD_NUM<=16
                .OPRD_SIGNED                    (OPRD_SIGNED                    ),
                .OPRD_REG                       (LVL_REG[2]                     )
        ) u_l3_add ( 
                .rst_n                          (rst_n                          ),
                .clk                            (clk                            ),
                .cke                            (cke                            ),

                .vld_in                         (l2_vld                         ),
                .oprds                          (l2_add                         ),

                .vld_out                        (l3_vld                         ),
                .sums                           (l3_add                         )
        );

        if (OPRD_NUM<=8) begin:G_L3O
            assign vld_out = l3_vld;
            assign sum_o = l3_add[0+:L3_BW];
        end else begin:G_L4
            localparam          L4_PAIR                 = L3_NUM/2;
            localparam          L4_ODD                  = L3_NUM%2;
            localparam          L4_NUM                  = L4_PAIR + L4_ODD;
            localparam          L4_BW                   = L3_BW+1;

            wire                                        l4_vld;
            wire                [L4_NUM*L4_BW-1:0]      l4_add;

            add_pair #(
                    .OPRD_BW                        (L3_BW                          ),	// bit width of single operand
                    .OPRD_NUM                       (L3_NUM                         ),	// number of operands, must 2<=OPRD_NUM<=16
                    .OPRD_SIGNED                    (OPRD_SIGNED                    ),
                    .OPRD_REG                       (LVL_REG[3]                     )
            ) u_l4_add ( 
                    .rst_n                          (rst_n                          ),
                    .clk                            (clk                            ),
                    .cke                            (cke                            ),

                    .vld_in                         (l3_vld                         ),
                    .oprds                          (l3_add                         ),

                    .vld_out                        (l4_vld                         ),
                    .sums                           (l4_add                         )
            );

            if (OPRD_NUM<=16) begin:G_L4O
                assign vld_out = l4_vld;
                assign sum_o = l4_add[0+:L4_BW];
            end else begin:G_L5
                localparam          L5_PAIR                 = L4_NUM/2;
                localparam          L5_ODD                  = L4_NUM%2;
                localparam          L5_NUM                  = L5_PAIR + L5_ODD;
                localparam          L5_BW                   = L4_BW+1;

                wire                                        l5_vld;
                wire                [L5_NUM*L5_BW-1:0]      l5_add;

                add_pair #(
                        .OPRD_BW                        (L4_BW                          ),	// bit width of single operand
                        .OPRD_NUM                       (L4_NUM                         ),	// number of operands, must 2<=OPRD_NUM<=16
                        .OPRD_SIGNED                    (OPRD_SIGNED                    ),
                        .OPRD_REG                       (LVL_REG[4]                     )
                ) u_l5_add ( 
                        .rst_n                          (rst_n                          ),
                        .clk                            (clk                            ),
                        .cke                            (cke                            ),

                        .vld_in                         (l4_vld                         ),
                        .oprds                          (l4_add                         ),

                        .vld_out                        (l5_vld                         ),
                        .sums                           (l5_add                         )
                );

                if (OPRD_NUM<=32) begin:G_L5O
                    assign vld_out = l5_vld;
                    assign sum_o = l5_add[0+:L5_BW];
                end else begin:G_L6
                    localparam          L6_PAIR                 = L5_NUM/2;
                    localparam          L6_ODD                  = L5_NUM%2;
                    localparam          L6_NUM                  = L6_PAIR + L6_ODD;
                    localparam          L6_BW                   = L5_BW+1;

                    wire                                        l6_vld;
                    wire                [L6_NUM*L6_BW-1:0]      l6_add;

                    add_pair #(
                            .OPRD_BW                        (L5_BW                          ),	// bit width of single operand
                            .OPRD_NUM                       (L5_NUM                         ),	// number of operands, must 2<=OPRD_NUM<=16
                            .OPRD_SIGNED                    (OPRD_SIGNED                    ),
                            .OPRD_REG                       (LVL_REG[5]                     )
                    ) u_l6_add ( 
                            .rst_n                          (rst_n                          ),
                            .clk                            (clk                            ),
                            .cke                            (cke                            ),

                            .vld_in                         (l5_vld                         ),
                            .oprds                          (l5_add                         ),

                            .vld_out                        (l6_vld                         ),
                            .sums                           (l6_add                         )
                    );

                    if (OPRD_NUM<=64) begin:G_L6O
                        assign vld_out = l6_vld;
                        assign sum_o = l6_add[0+:L6_BW];
                    end else begin:G_L7
                        localparam          L7_PAIR                 = L6_NUM/2;
                        localparam          L7_ODD                  = L6_NUM%2;
                        localparam          L7_NUM                  = L7_PAIR + L7_ODD;
                        localparam          L7_BW                   = L6_BW+1;

                        wire                                        l7_vld;
                        wire                [L7_NUM*L7_BW-1:0]      l7_add;

                        add_pair #(
                                .OPRD_BW                        (L6_BW                          ),	// bit width of single operand
                                .OPRD_NUM                       (L6_NUM                         ),	// number of operands, must 2<=OPRD_NUM<=16
                                .OPRD_SIGNED                    (OPRD_SIGNED                    ),
                                .OPRD_REG                       (LVL_REG[6]                     )
                        ) u_l7_add ( 
                                .rst_n                          (rst_n                          ),
                                .clk                            (clk                            ),
                                .cke                            (cke                            ),

                                .vld_in                         (l6_vld                         ),
                                .oprds                          (l6_add                         ),

                                .vld_out                        (l7_vld                         ),
                                .sums                           (l7_add                         )
                        );

                        if (OPRD_NUM<=128) begin:G_L7O
                            assign vld_out = l7_vld;
                            assign sum_o = l7_add[0+:L7_BW];
                        end else begin:G_NULL
`ifdef PARAMETER_NULL_CHECK  
                            oprd_num_null u_oprd_num_null();
`endif
                        end
                    end
                end
            end
        end
    end
end endgenerate

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

a_oprd_num: assert property (@(posedge clk) disable iff (!rst_n)
    (OPRD_NUM<=128)
) else begin
    $error("OPRD_NUM must <=128.");
    $stop;
end

// synopsys translate_on
`endif

endmodule
